<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
 * Ionize, creative CMS
 *
 * @package		Ionize
 * @author		Ionize Dev Team
 * @license		http://doc.ionizecms.com/en/basic-infos/license-agreement
 * @link		http://ionizecms.com
 * @since		Version 0.9.0
 */

// ------------------------------------------------------------------------

/**
 * Ionize Base Model
 * Extends the Model class and provides basic ionize model functionnalities
 *
 * @package		Ionize
 * @subpackage	Models
 * @category	Model
 * @author		Ionize Dev Team
 *
 */

class Base_model extends CI_Model
{
  /*
   * Stores if this model is already loaded or not.
   *
   */
  protected static $_inited = FALSE;


  public $db_group = 'default';


  public static $ci = NULL;

  /*
   * Table name
   *
   */
  public $table = NULL; 		// Table name

  /*
   * Table primary key column name
   *
   */
  public $pk_name = NULL;

  /*
   * Lang table of elements
   * For example, "page" has a corresponding lang table called "page_lang"
   *
   */
  public $lang_table 	= NULL;

  /*
   * Extended fields definition table
   * This table contains definition of each extended field
   *
   */
  public $extend_field_table = 	'extend_field';

  /*
   * Extended fields intances table.
   * This table contains all the extended fields data
   *
   */
  public $extend_fields_table = 	'extend_fields';

  /*
   * Extended fields prefix. Needs to be the same as the one defined in /models/base_model
   *
   */
  private $extend_field_prefix = 	'ion_';

  /*
   * Array of extended fields definition
   * Contains all the extended fields definition for a type of data.
   * "page" is a type of data.
   */
  protected $extend_fields_def = array();


  public $limit 	= NULL;		// Query Limit
  public $offset 	= NULL;		// Query Offset

  /*
   * Publish filter
   * true : the content is filtered on online and published values (default)
   * false : all content is returned
   *
   */
  protected static $publish_filter = TRUE;


  /*
   * Array of table names on which media can be linked
   *
   */
  protected $with_media_table = array('page', 'article');


  /*
   * Array of table names on which content elements can be linked
   *
   */
  protected $with_elements = array('page', 'article');


  /*
   * Elements definition table
   * This table contains definition of each element
   *
   */
  public $element_definition_table = 		'element_definition';
  public $element_definition_lang_table = 	'element_definition_lang';

  /*
   * Elements intances table.
   * This table contains all the elements instances
   *
   */
  public $element_table = 		'element';

  /*
   * Stores if we already got or not the elements definition
   * If we already got them, they don't need to be loaded once more...
   *
   */
  protected $got_elements_def = FALSE;

  /*
   * Array of elements definition
   * Contains all the elements definition.
   */
  protected $elements_def = array();

  /*
   * Array of languages
   *
   */
  protected $_languages = NULL;


  /*
   * Local store of list_fields results
   *
   */
  protected $_list_fields = array();


  // ------------------------------------------------------------------------


  /**
   * Constructor
   *
   * @access	public
   */
  public function __construct()
  {
    parent::__construct();

    /*if (is_null($this->db_group))
    {
      $active_group = 'default';
      include(APPPATH . 'config/database.php');
      $this->db_group = $active_group;
    }

    $this->db = $this->load->database($this->db_group, TRUE);

    if(self::$_inited)
    {
      return;
    }
    self::$_inited = TRUE;*/

    self::$ci =& get_instance();

// Doesn't work with multiple DB
//		$CI =& get_instance();
//		$CI->db = $CI->load->database($this->db_group, TRUE);
//		$this->db = $this->load->database($this->db_group, TRUE);

    // Unlock the publish filter (filter on publish status of each item)
    //if (Authority::can('access', 'admin'))
    //{
    //  self::unlock_publish_filter();
    //}
  }


  // ------------------------------------------------------------------------


  /**
   * Get the model table name
   *
   */
  public function get_table()
  {
    return $this->table;
  }

  // ------------------------------------------------------------------------


  /**
   * @param $where
   * @return array
   */
  public function get($where)
  {
    $this->db->from($this->table);
    if (is_array($where))
    {
      $where && $this->db->where($where);
    } elseif(is_string($where))
    {
      $where && $this->db->where($where, null, false);
    }
    $data = $this->db->get()->result_array();
    return $data;
  }

  /**
   * join
   * @param $table
   * @param $cond
   * @param string $type
   */
  public function join($table, $cond, $type = '')
  {
    $this->db->join($table, $cond, $type);
    return $this->db;
  }


  // ------------------------------------------------------------------------


  /**
   * Get a resultset Where
   *
   * @access	public
   * @param 	array	An associative array
   * @return	array	Result set
   *
   */
  public function get_where($where = NULL)
  {
    return $this->db->get_where($this->table, $where, $this->limit, $this->offset);
  }


  // ------------------------------------------------------------------------


  /**
   * Get all the records
   *
   * @param null $table
   *
   * @return mixed
   */
  public function get_all($table = NULL)
  {
    $table = (!is_null($table)) ? $table : $this->table;

    $query = $this->db->get($table);

    return $query->result_array();
  }


  // ------------------------------------------------------------------------


  /**
   * Get one row
   *
   * @access	public
   * @param 	int		The result id
   * @return	object	A row object
   *
   */
  public function get_row($id = NULL)
  {
    $this->db->where($this->pk_name, $id);
    $query = $this->db->get($this->table);

    return $query->row();
  }


  // ------------------------------------------------------------------------


  /**
   * Get one row_array
   *
   * @param null $where
   * @param null $table
   *
   * @return mixed
   */
  public function get_row_array($where = NULL, $table = NULL)
  {
    $table = ($table == NULL) ? $this->table : $table;

    if ( is_array($where) )
    {
      // Perform conditions from the $where array
      foreach(array('limit', 'offset', 'order_by', 'like') as $key)
      {
        if(isset($where[$key]))
        {
          call_user_func(array($this->db, $key), $where[$key]);
          unset($where[$key]);
        }
      }
      if (isset($where['where_in']))
      {
        foreach($where['where_in'] as $key => $value)
        {
          if ( ! empty($value))
            $this->db->where_in($key, $value);
        }
        unset($where['where_in']);
      }

      $this->db->where($where);
    }

    $query = $this->db->get($table);
    return $query->row_array();
  }

  // ------------------------------------------------------------------------


  /**
   * @param       $field
   * @param array $where
   * @param null  $table
   *
   * @return string
   *
   */
  public function get_group_concat($field, $where=array(), $table=NULL)
  {
    $table = is_null($table) ? $this->table : $table;

    $data = '';

    $this->_process_where($where, $table);

    $this->db->select('group_concat(distinct '.$field.') as result');
    $query = $this->db->get($table);

    if ( $query->num_rows() > 0)
    {
      $result = $query->row_array();
      if ( ! is_null($result['result']))
      {
        $data = $result['result'];
      }
    }

    return $data;
  }


  // ------------------------------------------------------------------------


  /**
   * @param       $field
   * @param array $where
   * @param null  $table
   *
   * @return array
   *
   */
  public function get_group_concat_array($field, $where=array(), $table = NULL)
  {
    $result = $this->get_group_concat($field, $where, $table);

    if ($result != '')
      return explode(',', $result);

    return array();
  }


  // ------------------------------------------------------------------------


  /**
   * Get array of records
   *
   * @access	public
   * @param 	array		An associative array
   * @param 	string		table name. Optional.
   * @return	array		Array of records
   *
   */
  public function get_list($where = NULL, $table = NULL)
  {
    $data = array();

    $table = (!is_null($table)) ? $table : $this->table;

    // Perform conditions from the $where array
    foreach(array('limit', 'offset', 'order_by', 'like') as $key)
    {
      if(isset($where[$key]))
      {
        call_user_func(array($this->db, $key), $where[$key]);
        unset($where[$key]);
      }
    }
    if (isset($where['where_in']))
    {
      foreach($where['where_in'] as $key => $value)
      {
        $this->db->where_in($key, $value);
      }
      unset($where['where_in']);
    }


    if ( !empty ($where) )
    {
      foreach($where as $cond => $value)
      {
        if (is_string($cond))
        {
          $this->db->where($cond, $value);
        }
        else
        {
          $this->db->where($value);
        }
      }
    }

    $this->db->select($table.'.*', FALSE);

    //	log_message('app', print_r($this->db->get_compile_select() , TRUE));
    $query = $this->db->get($table);
    if ( $query->num_rows > 0 )
      $data = $query->result_array();

    $query->free_result();

    return $data;
  }


  // ------------------------------------------------------------------------


  /**
   * Get element lang data (from lang table only)
   *
   * @param 	string	Element ID. Optional. If not set, returns all the lang table records
   * @param	array	Arraylist of all translations rows
   *
   */
  public function get_lang($where = NULL)
  {
    $data = $returned_data = array();

    if ($this->lang_table != '')
    {
      if ( is_array($where))
      {
        // Perform conditions from the $where array
        foreach(array('limit', 'offset', 'order_by', 'like') as $key)
        {
          if(isset($where[$key]))
          {
            call_user_func(array($this->db, $key), $where[$key]);
            unset($where[$key]);
          }
        }
        $this->db->where($where);
      }
      elseif ( ! is_null($where))
      {
        $this->db->where($this->pk_name, $where);
      }

      $query = $this->db->get($this->lang_table);

      if ( $query->num_rows() > 0 )
        $data = $query->result_array();

      $query->free_result();
    }

    // Set lang code as record index
    foreach($data as $lang_item)
      $returned_data[$lang_item['lang']] = $lang_item;

    return $returned_data;
  }


  // ------------------------------------------------------------------------


  /** Get post list with lang data
   *  Used by front-end to get the elements list with lang data
   *
   *	@param	array	WHERE array
   *	@param	string	Language code
   *	@param	number	Limit to x records
   *	@param	string	complete LIKE String
   *
   *	@return	array	The complete arrayList of element, including medias
   *
   */
  public function get_lang_list($where = array(), $lang = NULL)
  {
    $data = array();

    $this->_process_where($where);

    // Make sure we have only one time each element
    $this->db->distinct();

    // Lang data
    if ( ! is_null($lang))
    {
      $this->db->select($this->lang_table.'.*');
      $this->db->join(
        $this->lang_table,
        $this->lang_table.'.'.$this->pk_name.' = ' .$this->table.'.'.$this->pk_name .
        ' AND ' . $this->lang_table.'.lang = \'' . $lang . '\''
        ,
        'left'
      );
      // $this->db->where($this->lang_table.'.lang', $lang);
    }

    // Main data select
    $this->db->select($this->table.'.*', FALSE);

    $query = $this->db->get($this->table);

    if($query->num_rows() > 0)
    {
      $data = $query->result_array();
      $query->free_result();

      // Add linked medias to the "media" index of the data array
      if (in_array($this->table, $this->with_media_table))
        $this->add_linked_media($data, $this->table, $lang);

      // Add extended fields if necessary
      $this->add_extend_fields($data, $this->table, $lang);

      // Add URLs for each language
      if ($this->table == 'page' OR $this->table == 'article')
        $this->add_lang_urls($data, $this->table, $lang);
    }

    return $data;
  }


  // ------------------------------------------------------------------------


  /**
   * Get pages or articles from their lang URL
   *
   * @param 	Mixed	ID or array of IDs to exclude for the search
   *
   * @returns	Array	Array of elements
   *
   */
  public function get_from_urls($urls, $excluded_id)
  {
    $data = array();

    if ( ! is_array($urls))
      $urls = array_values(array($urls));

    // Main data select
    $this->db->select($this->table.'.*', FALSE);
    $this->db->join($this->lang_table, $this->lang_table.'.id_'.$this->table.' = ' .$this->table.'.id_'.$this->table, 'inner');
    $this->db->where_in($this->lang_table.'.url', $urls);

    // Add excluded IDs to the statement
    if ($excluded_id !='' && !is_array($excluded_id))
      $excluded_id = array($excluded_id);

    if ( !empty($excluded_id))
    {
      $this->db->where_not_in($this->lang_table.'.id_'.$this->table, $excluded_id);
    }


    $query = $this->db->get($this->table);

    if($query->num_rows() > 0)
    {
      $data = $query->result_array();
      $query->free_result();
    }

    return $data;
  }


  // ------------------------------------------------------------------------


  public function get_extend_fields_definition($parent = NULL, $lang = NULL)
  {
    if ( ! isset($this->extend_fields_def[$parent]))
    {
      if ( ! isset(self::$ci->extend_field_model))
        self::$ci->load->model('extend_field_model');

      if (is_null($lang))
        $this->extend_fields_def[$parent] = self::$ci->extend_field_model->get_list(array('extend_field.parent' => $parent));
      else
        $this->extend_fields_def[$parent] = self::$ci->extend_field_model->get_lang_list(array('extend_field.parent' => $parent), $lang);

    }
    return $this->extend_fields_def[$parent];
  }


  // ------------------------------------------------------------------------


  /**
   * Get the current linked childs items as a simple array from a N:M table
   *
   * @param	String		Items table name
   * @param	String		Parent table name
   * @param	Integer		Parent ID
   * @param	String		Link table prefix. Default to ''
   *
   * @return	array		items keys simple array
   *
   */
  public function get_joined_items_keys($items_table, $parent_table, $parent_id, $prefix='')
  {
    $data = array();

    // N to N table
    $link_table = $prefix.$parent_table.'_'.$items_table;

    // Items table primary key detection
    $fields = $this->db->list_fields($items_table);
    $items_table_pk = $fields[0];

    // Parent table primary key detection
    $fields = $this->db->list_fields($parent_table);
    $parent_table_pk = $fields[0];

    $this->db->where($parent_table_pk, $parent_id);
    $this->db->select($items_table_pk);
    $query = $this->db->get($link_table);

    foreach($query->result() as $row)
    {
      $data[] = $row->$items_table_pk;
    }

    return $data;
  }



  // ------------------------------------------------------------------------

  /**
   * Returns the content of a link table based on conditions in this table
   *
   * @param	String		Parent table name
   * @param	String		Child table name
   * @param	Array		Array of conditions
   * @param	int			Data from first or second table. Default 1
   * @param	String		Link table prefix. Default to ''
   *
   * @return	array		Array of Hashtable
   *
   */
  public function get_linked_items($first_table, $second_table, $cond, $join=1, $prefix = '')
  {
    $data = array();

    $second_pk_name = $this->get_pk_name($second_table);
    $first_pk_name = $this->get_pk_name($first_table);

    // N to N table
    $link_table = $prefix.$first_table.'_'.$second_table;

    // Correct ambiguous columns
    $cond = $this->correct_ambiguous_conditions($cond, $link_table);

    $this->db->from($link_table);
    $this->db->where($cond);

    if ($join == 2)
    {
      $this->db->join($second_table, $second_table.'.'.$second_pk_name.' = '.$link_table.'.'.$second_pk_name);
    }
    else
    {
      $this->db->join($first_table, $first_table.'.'.$first_pk_name.' = '.$link_table.'.'.$first_pk_name);
    }

    $query = $this->db->get();

    if($query->num_rows() > 0)
    {
      $data = $query->result_array();
    }

    return $data;
  }


  // ------------------------------------------------------------------------


  /**
   * Same as get_linked_items, but considering the language code
   *
   * @param	String		Parent table name
   * @param	String		Child table name
   * @param	Array		Array of conditions
   * @param	String		Lang code
   * @param	String		Link table prefix. Default to ''
   *
   * @return	array		Array of Hashtable
   *
   */
  public function get_linked_lang_items($parent_table, $child_table, $cond, $lang, $prefix = '')
  {
    $data = array();

    $child_pk_name = $this->get_pk_name($child_table);

    // N to N table
    $link_table = $prefix.$parent_table.'_'.$child_table;

    // Child lang table
    $child_lang_table = $child_table.'_lang';

    $this->db->from($link_table);
    $this->db->where($this->correct_ambiguous_conditions($cond,$link_table) );
    $this->db->where('lang', $lang);

    $this->db->join($child_table, $child_table.'.'.$child_pk_name.' = '.$link_table.'.'.$child_pk_name);
    $this->db->join($child_lang_table, $child_lang_table.'.'.$child_pk_name.' = '.$child_table.'.'.$child_pk_name);

    $query = $this->db->get();

    if($query->num_rows() > 0)
    {
      $data = $query->result_array();
    }

    return $data;
  }


  // ------------------------------------------------------------------------


  /**
   * @param        $field
   * @param array  $where
   * @param null   $nothing_index
   * @param null   $nothing_value
   * @param null   $order_by
   * @param string $glue
   *
   * @return array
   */
  public function get_form_select($field, $where=array(), $nothing_index = NULL, $nothing_value = NULL, $order_by = NULL, $glue="")
  {
    $table = $this->get_table();
    if ( ! is_null($table))
      return $this->get_items_select($table, $field, $where, $nothing_index, $nothing_value, $order_by, $glue );

    return array();
  }


  // ------------------------------------------------------------------------


  /**
   * Gets items key and value as an associative array
   *
   * @param        $items_table
   * @param        $field				index of the field to get
   * @param array  $where
   * @param null   $nothing_index		Value to display fo "no value"
   * @param null   $nothing_value
   * @param null   $order_by
   * @param string $glue
   *
   * @return array
   */
  public function get_items_select($items_table, $field, $where=array(), $nothing_index = NULL, $nothing_value = NULL, $order_by = NULL, $glue="")
  {
    $data = array();

    // Add the Zero value item
    if ( ! is_null($nothing_value))
    {
      $nothing_index = (is_null($nothing_index)) ? 0 : $nothing_index;
      $data = array($nothing_index => $nothing_value);
    }

    // Items table primary key detection
    $fields = $this->db->list_fields($items_table);
    $items_table_pk = $fields[0];

    // ORDER BY
    if ( ! is_null($order_by))
      $this->db->order_by($order_by);

    // WHERE
    if (is_array($where) && ! empty($where))
      $this->db->where($where);

    // Query
    $query = $this->db->get($items_table);

    foreach($query->result() as $row)
    {
      if (is_array($field))
      {
        $value = array();
        foreach($field as $f)
        {
          $value[] = $row->$f;
        }
        $data[$row->$items_table_pk] = implode($glue, $value);
      }
      else
      {
        $data[$row->$items_table_pk] = $row->$field;
      }
    }

    return $data;
  }


  // ------------------------------------------------------------------------


  /**
   * @param        $field
   * @param null   $lang
   * @param array  $where
   * @param null   $nothing_index
   * @param null   $nothing_value
   * @param null   $order_by
   * @param string $glue
   *
   * @return array
   */
  public function get_lang_form_select($field, $lang = NULL, $where=array(), $nothing_index = NULL, $nothing_value = NULL, $order_by = NULL, $glue="")
  {
    if (is_null($lang))
      $lang = Settings::get_lang();

    $table = $this->get_table();
    if ( ! is_null($table))
      return $this->get_lang_items_select($table,$field, $lang, $where, $nothing_index, $nothing_value, $order_by, $glue );

    return array();
  }


  // ------------------------------------------------------------------------


  /**
   * Same as get_items_select() but taking a lang table field as label
   *
   */
  public function get_lang_items_select($items_table, $field, $lang, $where=array(), $nothing_index = NULL, $nothing_value = NULL, $order_by = NULL, $glue="")
  {
    $data = array();

    // Add the Zero value item
    if ( ! is_null($nothing_value))
    {
      $nothing_index = (is_null($nothing_index)) ? 0 : $nothing_index;
      $data = array($nothing_index => $nothing_value);
    }

    // Items table primary key detection
    $fields = $this->db->list_fields($items_table);
    $items_table_pk = $fields[0];

    // ORDER BY
    if ( ! is_null($order_by))
      $this->db->order_by($order_by);

    // Join Lang table
    $this->db->join($items_table.'_lang', $items_table.'_lang.'.$items_table_pk.'='.$items_table.'.'.$items_table_pk);
    $this->db->where($items_table.'_lang.lang', $lang);

    // WHERE
    if (is_array($where) && ! empty($where))
      $this->db->where($where);

    // Query
    $query = $this->db->get($items_table);

    if($query->num_rows() > 0)
    {
      foreach($query->result() as $row)
      {
        if (is_array($field))
        {
          $value = array();
          foreach($field as $f)
          {
            $value[] = $row->$f;
          }
          $data[$row->$items_table_pk] = implode($glue, $value);
        }
        else
        {
          $data[$row->$items_table_pk] = $row->$field;
        }
      }
    }
    return $data;
  }


  // ------------------------------------------------------------------------


  public function get_unique_name($name, $id_to_exclude=NULL, $table=NULL, $postfix = 1)
  {
    $this->load->helper('text_helper');

    $table = (!is_null($table)) ? $table : $this->table;

    $name = url_title(convert_accented_characters($name));

    $where = array('name' => $name);

    if (!is_null($id_to_exclude) && $id_to_exclude != FALSE)
      $where['id_'.$table.' !='] = $id_to_exclude;

    $exists = $this->exists($where);

    if ($exists)
    {
      if ($postfix > 1 OR (substr($name, -2, count($name) -2) == '-' && intval(substr($name, -1)) != 0 ))
        $name = substr($name, 0, -2);

      $name = $name . '-' . $postfix;

      return $this->get_unique_name($name, $id_to_exclude, $table, $postfix + 1);
    }

    return $name;
  }


  // ------------------------------------------------------------------------


  public function get_unique_field($field, $value, $id_to_exclude=NULL, $table=NULL, $postfix = 1)
  {
    $this->load->helper('text_helper');

    $table = (!is_null($table)) ? $table : $this->table;

    $value = url_title(convert_accented_characters($value));

    $where = array($field => $value);

    if (!is_null($id_to_exclude) && $id_to_exclude != FALSE)
      $where['id_'.$table.' !='] = $id_to_exclude;

    $exists = $this->exists($where);

    if ($exists)
    {
      if ($postfix > 1 OR (substr($value, -2, count($value) -2) == '-' && intval(substr($value, -1)) != 0 ))
        $value = substr($value, 0, -2);

      $value = $value . '-' . $postfix;

      return $this->get_unique_field($field, $value, $id_to_exclude, $table, $postfix + 1);
    }

    return $value;
  }


  // ------------------------------------------------------------------------


  public function get_keys_array($field, $where = array(), $table = NULL)
  {
    $result = array();

    $table = (!is_null($table)) ? $table : $this->table;

    $this->_process_where($where, $table);

    $this->db->select("group_concat(".$field." separator ',') as ids", FALSE);

    $query = $this->db->get($table);

    $data = $query->row_array();

    if ( ! empty($data['ids']))
    {
      $result = explode(',', $data['ids']);
    }

    return $result;
  }


  // ------------------------------------------------------------------------


  /**
   * Return the max value of one given field
   *
   * @param      $field
   * @param null $table
   * @param null $where
   *
   * @return bool
   *
   */
  public function get_max($field, $table=NULL, $where=NULL)
  {
    $table = ( ! is_null($table)) ? $table : $this->table ;

    $this->db->select_max($field, 'maximum');

    if (! is_null($where))
    {
      $this->db->where($where);
    }

    $query = $this->db->get($table);

    if ($query->num_rows() > 0)
    {
      $row = $query->row();
      return $row->maximum;
    }
    return FALSE;
  }


  // ------------------------------------------------------------------------


  public function simple_search($term, $field, $limit)
  {
    $data = array();

    $this->db->like($this->table.'.'.$field, $term);

    $this->db->limit($limit);

    $query = $this->db->get($this->table);

    if($query->num_rows() > 0)
    {
      $data = $query->result_array();
    }

    return $data;
  }


  // ------------------------------------------------------------------------


  /**
   * Returns the first PK field nam found for the given table
   *
   */
  public function get_pk_name($table = NULL)
  {
    if (! is_null($table))
    {
      $fields = $this->db->field_data($table);

      foreach ($fields as $field)
      {
        if ($field->primary_key)
        {
          return $field->name;
          break;
        }
      }
    }
    else
    {
      return $this->pk_name;
    }
    return FALSE;
  }


  // ------------------------------------------------------------------------


  /**
   * Sets the current table
   *
   * @param string	table name
   *
   */
  public function set_table($table)
  {
    $this->table = $table;
  }


  // ------------------------------------------------------------------------


  /**
   * Sets the current table pk
   *
   * @param string	table pk name
   *
   */
  public function set_pk_name($pk_name)
  {
    $this->pk_name = $pk_name;
  }


  // ------------------------------------------------------------------------


  /**
   * Sets the current lang table name
   *
   * @param string	lang table name
   *
   */
  public function set_lang_table($table)
  {
    $this->lang_table = $table;
  }


  // ------------------------------------------------------------------------


  /**
   * Adds the current table to the the which have one media table
   *
   * @usage	In models :
   *			$this->set_with_media_table()
   *
   * @param string	table name
   *
   */
  public function set_with_media_table($table = NULL)
  {
    if ( is_null($table))
      $table = $this->table;

    if ( ! in_array($table, $this->with_media_table))
      array_push($this->with_media_table, $table);
  }


  // ------------------------------------------------------------------------


  /**
   * Get the elements definition and store them in the private property "elements_def"
   *
   * @param	String	Parent type
   * @return	Array	Extend fields definition array
   */
  protected function set_elements_definition($lang)
  {
    $CI =& get_instance();

    // Loads the model if it isn't loaded
    if (!isset($CI->element_definition_model))
      $CI->load->model('element_definition_model');

    // Get the extend fields definition if not already got
    if ($this->got_elements_def == FALSE)
    {
      // Store the extend fields definition
      $this->elements_def = $CI->element_definition_model->get_lang_list(FALSE, $lang);

      // Set this to true so we don't get the extend field def a second time for an object of same kind
      $this->got_elements_def = TRUE;
    }
  }


  // ------------------------------------------------------------------------


  /**
   * Save one element, including lang depending data
   *
   * @param 	array	Standard data table
   * @param 	array	Lang depending data table. optional.
   *
   * @return 	int		Saved element ID
   *
   */
  public function save($data, $dataLang = array())
  {
    /*
     * Base data save
     */
    $data = $this->clean_data($data);

    $id = FALSE;

    // Insert
    if( ! isset($data[$this->pk_name]) || $data[$this->pk_name] == '' )
    {
      // Remove the ID so the generated SQL will be clean (no empty String insert in the table PK field)
      unset($data[$this->pk_name]);

      $this->db->insert($this->table, $data);
      $id = $this->db->insert_id();
    }
    // Update or Insert
    else
    {
      $where = array($this->pk_name => $data[$this->pk_name]);

      // Update
      if( $this->exists($where, $this->table))
      {
        $this->db->where($this->pk_name, $data[$this->pk_name]);
        $this->db->update($this->table, $data);
      }
      // Insert
      else
      {
        $this->db->insert($this->table, $data);
      }

      $id = $data[$this->pk_name];
    }

    /*
     * Lang data save
     */
    if ( ! empty($dataLang) )
    {
      $dataLang = $this->clean_data_lang($dataLang);

      foreach(Settings::get_languages() as $language)
      {
        foreach($dataLang as $lang => $data)
        {
          if ( ! is_string($lang))
            $lang = isset($data['lang']) ? $data['lang'] : 0;

          if($lang === $language['lang'])
          {
            $where = array(
              $this->pk_name => $id,
              'lang' => $lang
            );

            // Update
            if ( ! empty($data))
            {
              if( $this->exists($where, $this->lang_table))
              {
                $this->db->where($where);
                $this->db->update($this->lang_table, $data);
              }
              // Insert
              else
              {
                // Correct lang & pk field on lang data array
                $data['lang'] = $lang;
                $data[$this->pk_name] = $id;

                $this->db->insert($this->lang_table, $data);
              }
            }
          }
        }
      }
    }
    return $id;
  }


  // ------------------------------------------------------------------------


  public function save_lang_data($id_item, $data, $lang_table = NULL)
  {
    $lang_table = (!is_null($lang_table)) ? $lang_table : $this->lang_table;

    $fields = $this->list_fields($lang_table);

    $rows = array();

    foreach($fields as $field)
    {
      if (isset($data[$field]) && is_array($data[$field]) )
      {
        foreach($data[$field] as $lang=>$value)
        {
          $rows[$lang][$field] = $value;
        }
      }
    }

    foreach($rows as $lang => $row)
    {
      $row[$this->pk_name] = $id_item;

      $row['lang'] = $lang;

      $where = array(
        $this->pk_name => $id_item,
        'lang' => $lang
      );

      // Update
      if( $this->exists($where, $lang_table))
      {
        $this->db->where($where);
        $this->db->update($lang_table, $row);
      }
      // Insert
      else
      {
        // Correct lang & pk field on lang data array
        $data['lang'] = $lang;
        $data[$this->pk_name] = $id_item;

        $this->db->insert($lang_table, $row);
      }
    }

    return $id_item;
  }


  // ------------------------------------------------------------------------


  /**
   * Saves ordering for items in the current table or in the join table, depending on parent var.
   *
   * @param	mixed	String of coma separated new order or array of order
   * @return	string	Coma separated order
   *
   */
  public function save_ordering($ordering, $parent = FALSE, $id_parent = FALSE)
  {
    if ( ! is_array($ordering))
    {
      $ordering = explode(',', $ordering);
    }
    $new_order = '';
    $i = 1;

    while (list ($rank, $id) = each ($ordering))
    {
      $this->db->where($this->pk_name, $id);
      $this->db->set('ordering', $i++);

      // If parent table is defined, save ordering in the join table
      if ($parent !== FALSE)
      {
        $parent_pk = $this->get_pk_name($parent);

        $this->db->where($parent.'_'.$this->table.'.'.$parent_pk, $id_parent);
        $this->db->update($parent.'_'.$this->table);
      }
      else
      {
        $this->db->update($this->table);
      }

      $new_order .= $id.",";
    }

    return substr($new_order, 0, -1);
  }


  // ------------------------------------------------------------------------


  /**
   * Save link between a parent and a child in a link table : N:N
   * The parent table is supposed to be the current table
   *
   * @param	Mixed	Parent table PK value
   * @param	String	Child table name
   * @param	Mixed	Child table PK value
   * @param	Array	Data to add to the link table
   * @param	String	Link table prefix.
   *
   */
  public function save_simple_link($parent_table, $id_parent, $child_table, $id_child, $context_data = array(), $prefix='')
  {
    $link_table = $prefix.$parent_table.'_'.$child_table;

    // PK fields
    $parent_pk_name = $this->get_pk_name($parent_table);
    $child_pk_name = $this->get_pk_name($child_table);

    if (FALSE == $this->exists(array($parent_pk_name => $id_parent, $child_pk_name => $id_child), $link_table) )
    {
      $data = array(
        $parent_pk_name => $id_parent,
        $child_pk_name => $id_child
      );

      if ( ! empty($context_data) )
      {
        // Cleans the context data array by removing data not in context table
        $context_data = $this->clean_data($context_data, $link_table);

        $data = array_merge($context_data, $data);
      }

      $this->db->insert($link_table, $data);

      return TRUE;
    }

    return FALSE;
  }


  // ------------------------------------------------------------------------


  /**
   * Join multiple items keys to a parent through a N:M table
   *
   * Items are consired as 'childs' and will be attached to a 'parent' through the join table.
   * That means before saving, all rows with the 'parent ID' key will be deleted in the join table.
   *
   * Note: 	When attaching 'categories' to an 'article', the category array will be considered as 'child'
   *			and the article as 'parent'.
   *			That means the join table MUST be named 'parent_child'.
   *			Example : ARTICLE_CATEGORY is the join table between articles and categories
   *			In that case, the tables ARTICLE and the table CATEGORY MUST exist
   *
   * @param	string		parent table name.
   * @param	int			parent ID
   * @param	string		items table name
   * @param	array		items to save. Simple array of keys.
   * @param	String		Link table prefix. Default to ''
   *
   * @return	int		number of attached items
   *
   */
//	function join_items_keys_to($child_table, $items, $parent_table, $parent_id, $prefix)
  public function save_multiple_links($parent_table, $parent_id, $child_table, $items, $prefix = '')
  {
    // N to N table
    $link_table = $prefix.$parent_table.'_'.$child_table;

    // PK fields
    $parent_pk_name = $this->get_pk_name($parent_table);
    $child_pk_name = $this->get_pk_name($child_table);


    // Delete existing link between items table and parent table
    $this->db->where($parent_pk_name, $parent_id);
    $this->db->delete($link_table);

    // nb inserted items
    $nb = 0;

    // Insert
    if ( !empty($items) )
    {
      foreach($items as $item)
      {
        if($item != 0 && $item !== FALSE)
        {
          $data = array(
            $parent_pk_name => $parent_id,
            $child_pk_name => $item
          );

          $this->db->insert($link_table, $data);
          $nb += 1;
        }
      }
    }

    return $nb;
  }


  // ------------------------------------------------------------------------


  /**
   * Unlink one parent and one child
   *
   * TODO : Replace function "delete_joined_keys" by this
   *
   * @param	Mixed	Parent table PK value
   * @param	String	Child table name
   * @param	Mixed	Child table PK value
   * @param	String	Link table prefix.
   *
   */
  public function delete_simple_link($parent_table, $id_parent, $child_table, $id_child, $prefix='')
  {
    // N to N table
    $link_table = $prefix.$parent_table.'_'.$child_table;

    // PK fields
    $parent_pk_name = $this->get_pk_name($parent_table);
    $child_pk_name = $this->get_pk_name($child_table);

    $this->db->where($parent_pk_name, $id_parent);
    $this->db->where($child_pk_name, $id_child);

    return (int) $this->db->delete($link_table);
  }

  // ------------------------------------------------------------------------


  /**
   * Add all media for one element to an array and returns this array
   *
   * @param	array	By ref. The array to add the media datas
   * @param	string	parent name. Example : 'page', 'article', etc.
   * @param	string	Lang code
   *
   */
  protected function add_linked_media(&$data, $parent, $lang = NULL)
  {
    // Select medias
    $this->db->select('*, media.id_media');
    $this->db->from('media,'. $parent .'_media');
    $this->db->where('media.id_media', $parent.'_media.id_media', FALSE);
    $this->db->order_by($parent.'_media.ordering');

    if ( ! is_null($lang))
    {
      $this->db->join('media_lang', 'media.id_media = media_lang.id_media', 'left outer');
      $this->db->where('(media_lang.lang =\'', $lang.'\' OR media_lang.lang is null )', FALSE);
    }

    $query = $this->db->get();

    $result = array();

    // Feed each media array
    if($query->num_rows() > 0)
    {
      $result = $query->result_array();
    }

    // If the data array is one simple array
    $data_is_simple_array = FALSE;
    if (! isset($data[0]) OR ! is_array($data[0]))
    {
      $data_is_simple_array = TRUE;
      $data = array(0 => $data);
    }

    foreach($data as $k=>$el)
    {
      $data[$k]['medias'] = array();
      foreach($result as $row)
      {
        if ($row[$this->pk_name] == $el[$this->pk_name])
          $data[$k]['medias'][] = $row;
      }

      // Add extended fields values for each media
      // Needs to be improved as the extend fieldsdefinition loaded in $this->extend_fields_def are these from the table and not from the medias...
      // But this has no importance, it's just not clean.
      if ( ! empty($data[$k]['medias']))
        $this->add_extend_fields($data[$k]['medias'], 'media', $lang);

      // Add file extension to each media
      foreach($data[$k]['medias'] as &$media)
      {
        $media['extension'] = pathinfo($media['file_name'], PATHINFO_EXTENSION);
        $media['mime'] = get_mime_by_extension($media['file_name']);
      }
    }

    if ($data_is_simple_array)
      $data = $data[0];

    $query->free_result();
  }


  // ------------------------------------------------------------------------


  /**
   * Adds to each element (page or article) the "urls" field, containing the URL for each language code
   *
   * @param	array	By ref. The array to add the urls datas
   * @param	string	parent name. Example : 'page', 'article', etc.
   */
  protected function add_lang_urls(&$data, $parent)
  {
    // Element ID
    $id = 'id_'.$parent;

    // If the data array is one simple array
    $data_is_simple_array = FALSE;
    if (! isset($data[0]) OR ! is_array($data[0]))
    {
      $data_is_simple_array = TRUE;
      $data = array(0 => $data);
    }

    // Array of IDs to get.
    $ids = array();
    foreach($data as $element)
    {
      $ids[] = $element[$id];
    }

    if ( ! empty($ids))
    {
      $this->db->select($id .', lang, online, url, title, subtitle');
      $this->db->where($id . ' in (' . implode(',' , $ids ) . ')' );
      $this->db->from($parent . '_lang');

      $query = $this->db->get();

      $result = array();

      // Feed each media array
      if($query->num_rows() > 0)
        $result = $query->result_array();

      $languages = Settings::get_languages();


      // data must be a list of arrays
      if (isset($data[0]) && is_array($data[0]))
      {
        foreach($data as $k => $el)
        {
          // New approach
          $data[$k]['languages'] = array();

          foreach($languages as $language)
          {
            $data[$k]['languages'][$language['lang']] = array(
              'lang' => $language['lang'],
              'online' => 0,
              'url' => NULL
            );

            foreach($result as $row)
            {
              if ($row[$id] == $el[$id] && $row['lang'] == $language['lang'])
              {
                // New approach
                $data[$k]['languages'][$language['lang']] = $row;

                // Old approach
                $data[$k]['urls'][$row['lang']] = $row['url'];
              }
            }
            // $url = array_values(array_filter($result, create_function('$row','return ($row["id_'.$this->table.'"] == "'. $el['id_'.$this->table] .'" && $row["lang"] == "'.$language['lang'].'");')));
            // $url = (!empty($url[0])) ? $url[0]['url'] : '';
            // $data[$k]['urls'][$language['lang']] = $url;
          }
        }
      }
    }

    if ($data_is_simple_array)
      $data = $data[0];
  }


  // ------------------------------------------------------------------------


  /**
   * Add extended fields and their values if website settings allow it.
   *
   * @param	Array	Data array. By ref.
   * @param	String	Parent type. can be "page", "article", etc.
   * @param	String	Lang code
   *
   */
  protected function add_extend_fields(&$data, $parent, $lang = NULL)
  {
    // get the extend fields definition array
    $efd = $this->get_extend_fields_definition($parent);

    if ( ! empty($efd) )
    {
      // If the data array is one simple array
      $data_is_simple_array = FALSE;
      if (! isset($data[0]) OR ! is_array($data[0]))
      {
        $data_is_simple_array = TRUE;
        $data = array(0 => $data);
      }

      // Get the elements ID to filter the SQL on...
      $ids = array();
      foreach ($data as $d)
      {
        if ( ! empty($d['id_'.$parent]))
          $ids[] = $d['id_'.$parent];
      }

      if ( ! empty($ids))
      {
        // Get the extend fields details, filtered on parents ID
        $this->db->where(array('extend_fields.parent'=>$parent));
        $this->db->where_in('extend_fields.id_parent', $ids);
        $this->db->join(
          $this->extend_fields_table,
          $this->extend_field_table.'.id_'.$this->extend_field_table.' = ' .$this->extend_fields_table.'.id_'.$this->extend_field_table,
          'inner'
        );

        $query = $this->db->get($this->extend_field_table);

        $extend_fields = array();
        if ( $query->num_rows() > 0)
          $extend_fields = $query->result_array();


        // Filter the result by lang : Only returns the not translated data and the given language translated data
        $filtered_result = array();
        foreach($extend_fields as $res)
        {
          if ($res['lang'] == $lang || $res['lang'] == '' )
            $filtered_result[] = $res;
        }

        // Attach each extend field to the corresponding data array
        foreach ($data as &$d)
        {
          // Store the extend definition array
          // Not usefull for the moment.
          // Can be used for debugging
          // $d['_extend_fields_definition'] = $this->get_extend_fields_definition($parent);

          // First set the extend fields of the data row to the default value. So it exists...
          foreach ($efd as $e)
          {
            if ($e['id_parent'] == 0 OR $d['id_'.$parent] == $e['id_parent'])
              $d[$this->extend_field_prefix.$e['name']] = $e['default_value'];
          }

          // Feeds the extend fields
          // Each extend field will be prefixed to avoid collision with standard fields names
          foreach ($filtered_result as $e)
          {
            if (empty($e['content']) && !empty($e['default_value']))
              $e['content'] = $e['default_value'];

            if ($d['id_'.$parent] == $e['id_parent'])
            {
              $d[$this->extend_field_prefix.$e['name']] = $e['content'];
            }
          }
        }
      }

      if ($data_is_simple_array)
        $data = $data[0];
    }
  }


  // ------------------------------------------------------------------------


  /**
   * Join multiple items keys to a parent through a N:M table
   *
   * Items are consired as 'childs' and will be attached to a 'parent' through the join table.
   * That means before saving, all rows with the 'parent ID' key will be deleted in the join table.
   *
   * Note: 	When attaching 'categories' to an 'article', the category array will be considered as 'child'
   *			and the article as 'parent'.
   *			That means the join table MUST be named 'parent_child'.
   *			Example : ARTICLE_CATEGORY is the join table between articles and categories
   *			In that case, the tables ARTICLE and the table CATEGORY MUST exist
   *
   * @param	string		items table name
   * @param	string/array		items to save. Simple array of keys.
   * @param	string		parent table name.
   * @param	int			parent ID
   *
   * @return	int		number of attached items
   *
   */
  public function join_items_keys_to($items_table, $items, $parent_table, $parent_id)
  {
    // N to N table
    $link_table = $parent_table.'_'.$items_table;

    // Items table primary key detection
    $fields = $this->db->list_fields($items_table);
    $items_table_pk = $fields[0];

    // Parent table primary key detection
    $fields = $this->db->list_fields($parent_table);
    $parent_table_pk = $fields[0];

    // Delete existing link between items table and parent table
    $this->db->where($parent_table_pk, $parent_id);
    $this->db->delete($link_table);

    // nb inserted items
    $nb = 0;

    // Insert
    if ( !empty($items) )
    {
      foreach($items as $item)
      {
        if($item != 0 && $item !== FALSE)
        {
          $data = array(
            $parent_table_pk => $parent_id,
            $items_table_pk => $item
          );

          $this->db->insert($link_table, $data);
          $nb += 1;
        }
      }
    }

    return $nb;
  }


  // ------------------------------------------------------------------------


  /**
   * Deletes one join row between an item and its parent
   *
   * @param	string		items table name
   * @param	int			item ID to delete
   * @param	string		parent table name.
   * @param	int			parent ID
   *
   * @return	int			number of affected rows
   *
   */
  public function delete_joined_key($items_table, $item_key, $parent_table, $parent_id)
  {
    // N to N table
    $link_table = $parent_table.'_'.$items_table;

    // Items table primary key detection
    $fields = $this->db->list_fields($items_table);
    $items_table_pk = $fields[0];

    // Parent table primary key detection
    $fields = $this->db->list_fields($parent_table);
    $parent_table_pk = $fields[0];

    $this->db->where(array(
      $parent_table_pk => $parent_id,
      $items_table_pk => $item_key
    ));

    return (int) $this->db->delete($link_table);
  }


  // ------------------------------------------------------------------------


  /**
   * Set an item online / offline depending on its current status
   *
   * @param	int			item ID
   *
   * @return 	boolean		New status
   *
   */
  public function switch_online($id)
  {
    // Current status
    $status = $this->get_row($id)->online;

    // New status
    ($status == 1) ? $status = 0 : $status = 1;

    // Save
    $this->db->where($this->pk_name, $id);
    $this->db->set('online', $status);
    $this->db->update($this->table);

    return $status;
  }


  // ------------------------------------------------------------------------


  /**
   * Feed the template array with data for each field in the table
   *
   * @param	int		ID of the search element
   * @param	array	By ref, the template array
   *
   */
  public function feed_template($id, &$template)
  {
    $data = $this->get($id);

    foreach($data as $key=>$val)
    {
      $template[$key] = $val;
    }

  }


  // ------------------------------------------------------------------------


  /**
   * Feed the template array with data for each field in language table
   *
   * @param $id
   * @param $template
   */
  public function feed_lang_template($id, &$template)
  {
    // lang_table fields
    $fields = NULL;
    $rows = $this->get_lang($id);

    $template['languages'] = array();

    foreach($this->get_languages() as $language)
    {
      $lang = $language['lang'];

      $template['languages'][$lang] = array();

      // Feeding of template languages elements
      foreach($rows as $row)
      {

        if(isset($row['lang']) && $row['lang'] == $lang)
        {
          $template['languages'][$lang] = $row;
          // $template[$lang] = $row;
        }
      }

      // Language empty : Feed with blank data
      if( empty($template['languages'][$lang]))
      {
        // Get lang_table fields if we don't already have them
        if (is_null($fields))
          $fields = $this->db->list_fields($this->lang_table);

        // If no fields here, no lang table exists, so feed nothing
        if ($fields)
        {
          foreach ($fields as $field)
          {
            if ($field != $this->pk_name)
              $template['languages'][$lang][$field] = '';
            else
              $template['languages'][$lang][$this->pk_name] = $id;
          }
        }
      }
    }
  }


  // ------------------------------------------------------------------------


  /**
   * Feeds the template array with blank data for each field in the table
   *
   * @param	array	By ref, the template array
   *
   */
  public function feed_blank_template(&$data = array())
  {
    $fields = $this->db->list_fields($this->table);

    $fields_data = $this->field_data($this->table);

    foreach ($fields as $field)
    {
      $field_data = array_values(array_filter($fields_data, create_function('$row', 'return $row["field"] == "'. $field .'";')));
      $field_data = (isset($field_data[0])) ? $field_data[0] : FALSE;

      $data[$field] = (isset($field_data['default'])) ? $field_data['default'] : '';
    }
    return $data;
  }


  // ------------------------------------------------------------------------


  /**
   * Feed the template array with blank data for each field in language table
   *
   * @param	array	By ref, the template array
   *
   */
  public function feed_blank_lang_template(&$template = FALSE)
  {
    if ($template == FALSE) $template = array();

    $fields = $this->db->list_fields($this->lang_table);

    $fields_data = $this->field_data($this->lang_table);

    $template['languages'] = array();

    foreach(Settings::get_languages() as $language)
    {
      $lang = $language['lang'];

      foreach ($fields as $field)
      {
        $field_data = array_values(array_filter($fields_data, create_function('$row', 'return $row["field"] == "'. $field .'";')));
        $field_data = (isset($field_data[0])) ? $field_data[0] : FALSE;

        $template['languages'][$lang][$field] = (isset($field_data['default'])) ? $field_data['default'] : '';
      }
    }
    return $template;
  }


  // ------------------------------------------------------------------------


  /**
   * Switch the publish filter off
   *
   */
  public function unlock_publish_filter()
  {
    $uri_string_to_array = explode('/', preg_replace("|^\/?|", '/', self::$ci->uri->uri_string));

    if( ! in_array(config_item('admin_url'), $uri_string_to_array))
    {
      // Settings::get('display_front_offline_content') not available here
      $this->db->where('name', 'display_front_offline_content');
      $query = $this->db->get('setting');
      $result = $query->row_array();

      if ( Authority::can('access', 'admin') && ( ! empty($result['content']) && $result['content'] == '1'))
      {
        self::$publish_filter = FALSE;
      }
    }
    else
      self::$publish_filter = FALSE;
  }


  // ------------------------------------------------------------------------


  /**
   * Insert a row
   *
   * @access	public
   * @param 	array	An associative array of data
   * @return	the last inserted id
   *
   */
  public function insert($data = NULL, $table = FALSE)
  {
    $table = (FALSE !== $table) ? $table : $this->table;

    $data = $this->clean_data($data, $table);

    $this->db->insert($table, $data);

    return $this->db->insert_id();
  }


  public function insert_ignore($data = NULL, $table = FALSE)
  {
    $table = (FALSE !== $table) ? $table : $this->table;

    $data = $this->clean_data($data, $table);

    $insert_query = $this->db->insert_string($table, $data);
    $insert_query = str_replace('INSERT INTO','INSERT IGNORE INTO',$insert_query);
    $inserted = $this->db->query($insert_query);

    return $inserted;
  }


  // ------------------------------------------------------------------------


  /**
   * Update a row
   *
   * @access	public
   *
   * @param 	Mixed		Where condition. If single value, PK of the table
   * @param 	array		An associative array of data
   * @param 	String		Table name. If not set, current models table
   *
   * @return	int			Number of updated rows
   *
   */
  public function update($where = NULL, $data = NULL, $table = FALSE)
  {
    $table = (FALSE !== $table) ? $table : $this->table;

    $data = $this->clean_data($data, $table);

    if ( is_array($where) )
    {
      $this->db->where($where);
    }
    else
    {
      $this->db->where($where, null, false);
    }

    $this->db->update($table, $data);

    return (int) $this->db->affected_rows();
  }


  // ------------------------------------------------------------------------


  /**
   * Delete row(s)
   *
   * @param null $where		Where condition. If single value, PK of the table
   * @param null $table
   *
   * @return int				Affected rows
   */
  public function delete($where = NULL, $table = NULL)
  {
    $table = ! empty($table) ? $table : $this->table;

    if ( is_array($where) )
    {
      $this->_process_where($where, $table);
      // $this->db->where($where);
    }
    else
    {
      $pk_name = $this->get_pk_name($table);
      $this->db->where($pk_name, $where);
    }

    $this->db->delete($table);

    return (int) $this->db->affected_rows();
  }


  // ------------------------------------------------------------------------


  /**
   * Count all rows corresponding to the passed conditions
   *
   * @param array $where
   * @param null  $table
   *
   * @return int
   */
  public function count($where = array(), $table = NULL)
  {
    $table = (!is_null($table)) ? $table : $this->table;

    // Perform conditions from the $where array
    foreach(array('limit', 'offset', 'order_by', 'like') as $key)
    {
      if(isset($where[$key]))
      {
        call_user_func(array($this->db, $key), $where[$key]);
        unset($where[$key]);
      }
    }

    if (isset($where['where_in']))
    {
      foreach($where['where_in'] as $key => $value)
      {
        $this->db->where_in($key, $value);
      }
      unset($where['where_in']);
    }

    if ( is_array($where) && ! empty($where) )
    {
      $this->db->where($where);
    }

    $query = $this->db->count_all_results($table);

    return (int) $query;
  }


  // ------------------------------------------------------------------------


  /**
   * Count all rows in a table or count all results from the current query
   *
   * @access	public
   * @param	bool	true / false
   * @return	int 	The number of all results
   *
   */
  public function count_all($results = FALSE)
  {
    if($results !== FALSE)
    {
      $query = $this->db->count_all_results($this->table);
    }
    else
    {
      $query = $this->db->count_all($this->table);
    }

    return (int) $query;
  }


  // ------------------------------------------------------------------------


  /**
   * Count items based on filter
   *
   * @param array $where
   * @param null  $table
   *
   * @return mixed
   */
  public function count_where($where = array(), $table =NULL)
  {
    $table = ( ! is_null($table)) ? $table : $this->get_table();

    if (isset($where['order_by']))
      unset($where['order_by']);

    if(isset($where['like']))
    {
      $this->db->like($where['like']);
      unset($where['like']);
    }

    if ( ! empty ($where) )
    {
      foreach($where as $cond => $value)
      {
        if (is_string($cond))
        {
          $this->db->where($cond, $value);
        }
        else
        {
          $this->db->where($value);
        }
      }
    }

    $this->db->from($table);

    return $this->db->count_all_results();
  }


  // ------------------------------------------------------------------------


  /**
   * Empty table
   *
   * @access	public
   * @return	void
   *
   */
  public function empty_table()
  {
    $this->db->empty_table($this->table);
  }


  // ------------------------------------------------------------------------


  /**
   * Checks if one table is empty, based on given conditions
   *
   * @param null $where
   * @param null $table
   *
   * @return bool
   */
  public function is_empty($where = NULL, $table = NULL)
  {
    $table = ! is_null($table) ? $table : $this->table;

    if ( is_array($where) )
    {
      $this->_process_where($where, $table);
    }

    $query = $this->db->get($table);

    if($query->num_rows() > 0)
    {
      return FALSE;
    }

    return TRUE;
  }


  // ------------------------------------------------------------------------


  /**
   * Check if a record exists in a table
   *
   * @param	array	conditions
   * @param	string	table name
   *
   * @access	public
   * @return	boolean
   *
   */
  public function exists($where = NULL, $table = NULL)
  {
    $table = ( ! is_null($table)) ? $table : $this->table ;
    is_string($where) && $this->db->where($where, null, false);
    is_array($where) && $this->db->where($where);
    $query = $this->db->from($table)->get();

    if ($query->num_rows() > 0)
      return TRUE;
    else
      return FALSE;
  }


  // --------------------------------------------------------------------


  /**
   * Checks if one element with a given field name already exists
   * in the DB.
   *
   * @param      $field			Field to test on
   * @param      $value			Value of the field to test on
   * @param null $element_id		Optional element ID (in case of change of the field value)
   * @param null $table
   *
   * @return bool
   *
   */
  public function check_exists($field, $value, $element_id = NULL, $table = NULL)
  {
    $item = $this->get_row_array(array($field => $value), $table);

    if ( ! is_null($element_id) && $element_id != FALSE)
    {
      $pk_name = $this->get_pk_name($table);

      if ( ! empty($item) && $item[$pk_name] != $element_id)
        return TRUE;
    }
    else
    {
      if ( ! empty($user))
        return TRUE;
    }
    return FALSE;
  }


  // ------------------------------------------------------------------------


  /**
   * Returns the table's fields array list
   *
   * array(
   *      'field' =>      'Field name'
   *      'type' =>       'DB field type' (int, tinyint, varchar, ...)
   *      'null' =>       TRUE / FALSE
   *      'key' =>        'PRI|MUL'
   *      'extra' =>      column extra
   *      'comment' =>    column comment
   *      'privileges' => column privileges
   *      'length' =>     int / array (in case of ENUM)
   * )
   *
   * @param	String		Table name
   * @param   Boolean     With / Without primary key. Default FALSE : without.
   *
   * @return	Array		Array of fields data
   *
   */
  public function field_data($table=NULL, $with_pk = FALSE)
  {
    $data = array();

    $table = ( ! is_null($table)) ? $table : $this->table ;

    $query = $this->db->query("SHOW FULL COLUMNS FROM " . $table);

    $fields = $query->result_array();

    foreach($fields as $key => $field)
    {
      if ($with_pk === FALSE)
      {
        if ($field['Field'] == $this->pk_name)
          continue;
      }

      // keys to lowercase
      $field = array_change_key_case($field, CASE_LOWER);
      $name = $field['field'];
      $data[$name] = $field;

      // isolate the DB type (remove size)

      $type = preg_split ("/[\s()]+/", $field['type']);

      if ($type[0] == 'enum')
      {
        $enum_values = preg_replace("/[enum'()]+/", "", $field['type']);
        $type[1] = explode(',', $enum_values);
      }

      $data[$name] = array_merge(
        $data[$name],
        array(
          'type' =>   $type[0],
          'null' =>   $field['null'] == 'YES' ? TRUE : FALSE,
          'length' => isset($type[1]) ? $type[1] : NULL,
          'value' =>  NULL
        )
      );
    }

    return $data;
  }


  // ------------------------------------------------------------------------


  /**
   * Check for a table field
   *
   * @param	String		Table name
   * @param null $table
   *
   * @return	Boolean		True if the field is found
   *
   */
  public function has_field($field, $table = NULL)
  {
    $table = ( ! is_null($table)) ? $table : $this->table ;

    $fields = $this->db->list_fields($table);

    if (in_array($field, $fields)) return TRUE;

    return FALSE;
  }


  // ------------------------------------------------------------------------


  /**
   * Removes from the data array the index which are not in the table
   *
   * @param      $data		The data array to clean
   * @param bool $table		Reference table. $this->table if not set.
   *
   * @return array
   */
  public function clean_data($data, $table = FALSE)
  {
    $cleaned_data = array();

    if ( ! empty($data))
    {
      $table = ($table !== FALSE) ? $table : $this->table;

      $fields = $this->db->list_fields($table);

      $fields = array_fill_keys($fields,'');

      $cleaned_data = array_intersect_key($data, $fields);
    }
    foreach($cleaned_data as $key=>$row)
    {
      if (is_array($row))
        unset($cleaned_data[$key]);
    }
    return $cleaned_data;
  }

  // ------------------------------------------------------------------------


  /**
   *	Reorders the ordering field correctly after unlink of one element
   *
   *	SET @rank=0;
   *	SET @rank=0;
   *	update table set ordering = @rank:=@rank+1
   *	where ...
   *	ORDER BY ordering ASC;
   *
   */
  public function reorder($table = NULL, $where = array())
  {
    $table = ( ! is_null($table)) ? $table : $this->table ;

    if ($this->has_field('ordering', $table))
    {
      $query = $this->db->query("SET @rank=0");

      // Perform conditions from the $where array
      foreach(array('limit', 'offset', 'order_by', 'like') as $key)
      {
        if(isset($where[$key]))
        {
          call_user_func(array($this->db, $key), $where[$key]);
          unset($where[$key]);
        }
      }

      $this->db->order_by('ordering ASC');
      $this->db->set('ordering', '@rank:=@rank+1', FALSE);
      $this->db->where($where);

      return $this->db->update($table);
    }
  }


  // ------------------------------------------------------------------------


  /**
   * Correct ambiguous target fields in SQL conditions
   *
   * @param	Array	condition array
   * @param	String	Table name
   *
   * @return	Array	Corrected condition array
   *
   */
  public function correct_ambiguous_conditions($array, $table)
  {
    if (is_array($array))
    {
      foreach ($array as $key => $val)
      {
        unset($array[$key]);
        $key = $table.'.'.$key;
        $array[$key] = $val;
      }

      return $array;
    }
  }


  // ------------------------------------------------------------------------


  /**
   * 	Required method to get the database group for THIS model
   */
  public function get_database_group()
  {
    return $this->db_group;
  }

  // ------------------------------------------------------------------------


  /**
   * List fields from one table of the current DB group
   * and stores the result locally.
   *
   * @param	string
   * @return	Array	List of table fields
   *
   */
  public function list_fields($table = NULL)
  {
    $table = ( ! is_null($table)) ? $table : $this->table ;

    if (isset($this->_list_fields[$this->db_group.'_'.$table]))
      return $this->_list_fields[$this->db_group.'_'.$table];

    $this->_list_fields[$this->db_group.'_'.$table] = $this->db->list_fields($table);

    return $this->_list_fields[$this->db_group.'_'.$table];
  }


  // ------------------------------------------------------------------------


  /**
   * Processes the query condition array
   * @param      $where
   * @param null $table
   */
  protected function _process_where($where, $table=NULL)
  {
    $table = ! empty($table) ? $table : $this->table;

    // Perform conditions from the $where array
    if ( ! empty($where) && is_array($where) )
    {
      foreach(array('limit', 'offset', 'like') as $key)
      {
        if(isset($where[$key]))
        {
          call_user_func(array($this->db, $key), $where[$key]);
          unset($where[$key]);
        }
      }

      if (isset($where['order_by']))
      {
        $this->db->order_by($where['order_by'], NULL, FALSE);
        unset($where['order_by']);
      }

      if (isset($where['where_in']))
      {
        foreach($where['where_in'] as $key => $values)
        {
          $processed = FALSE;
          foreach($values as $k => $value)
          {
            if (strtolower($value) == 'null')
            {
              unset($values[$k]);
              $this->db->where("(" . $key . " in ('".implode("','", $values)."') OR ".$key." IS NULL)", NULL, FALSE);
              $processed = TRUE;
              break;
            }
          }
          if ( ! $processed)
            $this->db->where_in($key, $values, FALSE);
        }
        unset($where['where_in']);
      }

      $protect = TRUE;

      foreach ($where as $field => $value)
      {
        $protect = ! (in_array(substr($field, -3), array(' in', ' is')));
        if ( $protect)
          $protect = ! (substr($field, -5) == ' like');

        if (is_string($field))
        {
          if ($value == 'NULL' && is_string($value))
          {
            $this->db->where($field. ' IS NULL', NULL, FALSE);
          }
          elseif($field == "RAW")
          {
            $this->db->where($value, NULL, FALSE);
          }
          else
          {
            if (strpos($field, '.') > 0)
            {
              $this->db->where($field, $value, $protect);
            }
            else
            {
              $this->db->where($table.'.'.$field, $value, $protect);
            }
          }
        }
        else
        {
          $this->db->where($value);
        }
      }
    }
  }

  public function last_query()
  {
    return $this->db->last_query();
  }

  public function table_exists($table)
  {
    return $this->db->table_exists($table);
  }
}
